package org.artifact.core.lang;


import java.util.Arrays;

public class RingBuffer<T> {

    public T[] elements = null;

    /**
     * buff大小
     */
    public int capacity = 0;
    /**
     * 写位子
     */
    public int writePos = 0;
    /**
     * 读位子
     */
    public int readPos = 0;
    /**
     * 翻转
     */
    public boolean flipped = false;

    /**
     * 初始化
     *
     * @param capacity 容量
     */
    public RingBuffer(int capacity) {
        this.capacity = capacity;
        this.elements = (T[]) new Object[capacity];
    }

    /**
     * 重置
     */
    public void reset() {
        this.writePos = 0;
        this.readPos = 0;
        this.flipped = false;
    }

    /**
     * 可读数量
     *
     * @return
     */
    public int available() {
        if (!flipped) {
            return writePos - readPos;
        }
        return capacity - readPos + writePos;
    }


    /**
     * 可写数量
     *
     * @return
     */
    public int remainingCapacity() {
        if (!flipped) {
            return capacity - (writePos - readPos);
//            return capacity - writePos;
        }
        return readPos - writePos;
    }

    /**
     * 容量
     *
     * @return
     */
    public int capacity() {
        return capacity;
    }


    public boolean put(T element) {
        if (!flipped) {
            // 沒有翻转
            if (writePos == capacity) {
                // 已经写到最后需要执行翻转 & 重置写的下标
                writePos = 0;
                flipped = true;

                if (writePos < readPos) {
                    // 如果还有空间执行写入
                    elements[writePos++] = element;
                    return true;
                } else {
                    // 数组中全是等待读取的数据,及已经写满一圈
                    return false;
                }
            } else {
                // 正常写入
                elements[writePos++] = element;
                return true;
            }
        } else {
            // 如果还有空间执行写入
            if (writePos < readPos) {
                elements[writePos++] = element;
                return true;
            } else {
                return false;
            }
        }
    }

    public int put(T[] newElements, int length) {
        int newElementsReadPos = 0;
        if (!flipped) {
            // 有足够本次写入的空间
            if (length <= capacity - writePos) {
                for (; newElementsReadPos < length; newElementsReadPos++) {
                    this.elements[this.writePos++] = newElements[newElementsReadPos];
                }
                return newElementsReadPos;
            } else {
                // 当前能写入多少写多少
                for (; this.writePos < capacity; this.writePos++) {
                    this.elements[this.writePos] = newElements[newElementsReadPos++];
                }

                // 翻转标记 & 写入剩余的
                this.writePos = 0;
                this.flipped = true;
                int endPos = Math.min(this.readPos, length - newElementsReadPos);
                for (; this.writePos < endPos; this.writePos++) {
                    this.elements[writePos] = newElements[newElementsReadPos++];
                }
                return newElementsReadPos;
            }

        } else {
            int endPos = Math.min(this.readPos, this.writePos + length);
            for (; this.writePos < endPos; this.writePos++) {
                this.elements[this.writePos] = newElements[newElementsReadPos++];
            }
            return newElementsReadPos;
        }
    }


    public T take() {
        if (!flipped) {
            // 没有翻转正常读取
            if (readPos < writePos) {
                return elements[readPos++];
            } else {
                return null;
            }
        } else {
            // 已经翻转，必须要读到最后才能重置读取下标
            if (readPos == capacity) {
                readPos = 0;
                flipped = false;

                if (readPos < writePos) {
                    return elements[readPos++];
                } else {
                    return null;
                }
            } else {
                return elements[readPos++];
            }
        }
    }

    public void skipBytes(int length) {
        length = Math.min(length, available());
        if (!flipped) {
            this.readPos = Math.min(this.writePos, this.readPos + length);
        } else {
            if (length <= capacity - readPos) {
                this.readPos += length;
            } else {
                int pos = capacity - this.readPos;
                this.readPos = 0;
                this.flipped = false;
                this.readPos = Math.min(this.writePos, length - pos);
            }
        }
    }

    public int take(T[] into, int length) {
        int intoWritePos = 0;
        if (!flipped) {
            int endPos = Math.min(this.writePos, this.readPos + length);
            for (; this.readPos < endPos; this.readPos++) {
                into[intoWritePos++] = this.elements[this.readPos];
            }
            return intoWritePos;
        } else {

            if (length <= capacity - readPos) {
                //length is lower than the elements available at the top
                //of the elements array - copy directly
                for (; intoWritePos < length; intoWritePos++) {
                    into[intoWritePos] = this.elements[this.readPos++];
                }

                return intoWritePos;
            } else {
                //length is higher than elements available at the top of the elements array
                //split copy into a copy from both top and bottom of elements array.

                //copy from top
                for (; this.readPos < capacity; this.readPos++) {
                    into[intoWritePos++] = this.elements[this.readPos];
                }

                //copy from bottom
                this.readPos = 0;
                this.flipped = false;
                int endPos = Math.min(this.writePos, length - intoWritePos);
                for (; this.readPos < endPos; this.readPos++) {
                    into[intoWritePos++] = this.elements[this.readPos];
                }

                return intoWritePos;
            }
        }
    }

    @Override
    public String toString() {
        return "RingBuffer [elements=" + Arrays.toString(elements) + ", capacity=" + capacity + ", writePos=" + writePos
                + ", readPos=" + readPos + ", flipped=" + flipped + "]";
    }


}